// nettest.cc 
//	Test out message delivery between two "Nachos" machines,
//	using the Post Office to coordinate delivery.
//
//	Two caveats:
//	  1. Two copies of Nachos must be running, with machine ID's 0 and 1:
//		./nachos -m 0 -o 1 &
//		./nachos -m 1 -o 0 &
//
//	  2. You need an implementation of condition variables,gmake
//	     which is *not* provided as part of the baseline threads 
//	     implementation.  The Post Office won't work without
//	     a correct implementation of condition variables.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "server.h"
#include "messageutil.h"

void AcquireLock(int lock){
	printf("Attempting to acquire lock[%d]\n",lock);
	char* data = "Acq Lock";
	//acquire lock
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, LOCK_ACQUIRE, lock, NULL, NULL, NULL);
	//now we wait till the server has let us acquire lock serverDataLock
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Acquired lock[%d]\n",lock);
}

void ReleaseLock(int lock){
	printf("Attempting to release lock[%d]\n",lock);
	char* data = "Rel Lock";
	//release lock
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, LOCK_RELEASE, lock, NULL, NULL, NULL);
	//now we wait till the server has let us acquire lock serverDataLock
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Released lock[%d]\n",lock);
}

void WaitCond(int lock, int cond){
	printf("Going to wait on cond[%d] for lock[%d]\n",cond,lock);
	char* data = "CV Wait";
	//put ourselves to sleep with CV on lock
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, COND_WAIT, lock, cond, NULL, NULL);
	//now we wait till the server will awaken us once someone has signaled
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Done waiting on cond[%d] for lock[%d]\n",cond,lock);
}

void SignalCond(int lock, int cond){
	printf("Going to signal cond[%d] for lock[%d]\n",cond,lock);
	char* data = "CV Signal";
	//signal CV on lock
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, COND_SIGNAL, lock, cond, NULL, NULL);
	//now we wait till the server will awaken us once someone has signaled
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Signaled cond[%d] for lock[%d]\n",cond,lock);
}

void BroadcastCond(int lock, int cond){
	printf("Going to broadcast cond[%d] for lock[%d]\n",cond,lock);
	char* data = "CV Broadcast";
	//braodcast CV on lock
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, COND_BROADCAST, lock, cond, NULL, NULL);
	//now we wait till the server will awaken us once someone has signaled
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Broadcasted cond[%d] for lock[%d]\n",cond,lock);
}

void UpdateMonVar(int ind, int val){
	printf("Creating Monitor Variable\n");
	char* data = "Create MV";
	//Update or set a MV value
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, UPDATE_MV, NULL, NULL, ind, val);
	//now we wait till the server will awaken us once someone has signaled
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Created Monitor Variable\n");
}

int RetrieveMonVar(int ind){
	printf("Retrieving Monitor Variable\n");
	char* data = "Create MV";
	//signal CV on lock
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, RETRIEVE_MV, NULL, NULL, ind, NULL);
	//now we wait till the server will awaken us once someone has signaled
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Retrieved Monitor Variable with value=%d\n",temp);
	return temp;
}

int CreateLock(){
	printf("Creating Lock\n");
	char* data = "Create Lock";
	//signal CV on lock
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, CREATE_LOCK, NULL, NULL, NULL, NULL);
	//now we wait till the server will awaken us once someone has signaled
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Created Lock at index %d\n",temp);
	return temp;
}

int CreateCV(){
	printf("Creating CV\n");
	char* data = "Create Lock";
	//signal CV on lock
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, THREAD_TO, CREATE_CV, NULL, NULL, NULL, NULL);
	//now we wait till the server will awaken us once someone has signaled
	int temp = msgUtil->ReceiveMessage(THREAD_TO);
	printf("Created CV at index %d\n",temp);
	return temp;
}


//Each SimpleThread called will attempt to acquire the serverDataLock, and then once they have acquired it, release it
void SimpleThread(){
	
	int lock = CreateLock();
	AcquireLock(lock);
	ReleaseLock(lock);
	
	//we have now released serverDataLock and our thread is now over
}

void Userprog1_Thread1(){
	
	//ServerDataLock is automatically created when the server is created
	int lock = CreateLock();
	int cv = CreateCV();
	
	AcquireLock(lock);
	//UpdateMonVar(mvIndex, mvValue);
	//int temp = RetrieveMonVar(mvIndex);
	//temp++;
	//UpdateMonVar(mvIndex, temp);
	WaitCond(lock,cv);
	AcquireLock(lock);
	//UpdateMonVar(mvIndex, 7);
	ReleaseLock(lock);
	
	//we have now released serverDataLock and our thread is now over
}

void Userprog1_Thread2(){
	int lock = 0;
	int cv = 0;
	
	AcquireLock(lock);
	BroadcastCond(lock,cv);
	ReleaseLock(lock);
	
	//we have now released serverDataLock and our thread is now over
}

void Userprog1_Thread3(){
	int lock = 0;
	int cv = 0;
	
	AcquireLock(lock);
	SignalCond(lock,cv);
	ReleaseLock(lock);
	
	//we have now released serverDataLock and our thread is now over
}

void Userprog1_Thread4(){
	//ServerDataLock is automatically created when the server is created
	int lock = 2;
	int CV = 1;
	int mvIndex = 1;
	int mvValue = 3;
	
	CreateLock();
	AcquireLock(lock);
	UpdateMonVar(mvIndex, mvValue);
	int temp = RetrieveMonVar(mvIndex);
	temp++;
	UpdateMonVar(mvIndex, temp);
	WaitCond(lock,CV);
	AcquireLock(lock);
	UpdateMonVar(mvIndex, 7);
	ReleaseLock(lock);
	
	//we have now released serverDataLock and our thread is now over
}

void Userprog1_Thread5(){
	//ServerDataLock is automatically created when the server is created
	int lock = CreateLock();
	int CV = CreateCV();
	
	AcquireLock(lock);
	BroadcastCond(lock,CV);
	ReleaseLock(lock);
	
	//we have now released serverDataLock and our thread is now over
}

void ServerManager(){
	printf("Server created\n");
	PacketHeader inPktHdr;
    MailHeader inMailHdr;
	//server that manages the locks and CVs
	Server* server = new Server();
	char buffer[MaxMailSize];
	bool success;
	char *response = "Serv resp";
	
	while(true){
		postOffice->Receive(SERVER_TO, &inPktHdr, &inMailHdr, buffer);
		printf("Got \"%s\" in (addr %d box %d) from (addr %d box %d)\n",buffer,inPktHdr.to,inMailHdr.to,inPktHdr.from,inMailHdr.from);
		fflush(stdout);
		
		//only machine ID=0 can send the command to kill the server
		if (inPktHdr.from == KILL_SERVER){
			break;
		}
	
		server->ReceiveMessage(inMailHdr.messageType,inPktHdr.from,inMailHdr.lockID,inMailHdr.condID,inMailHdr.mvIndex,inMailHdr.mvValue);
		
		int PktTo = server->GetNextPktDest();
		//if there are packets in the ready queue, continue until all packets have been processed
		while (PktTo != -1){
			//if call was not to retrieve MV
			switch (inMailHdr.messageType) {
				default:
					msgUtil->SendMessage(response, PktTo, THREAD_TO, SERVER_TO, NULL, NULL, NULL, NULL, NULL);
				case RETRIEVE_MV: 
					//msgUtil->SendMessage(response, PktTo, THREAD_TO, SERVER_TO, NULL, NULL, NULL, NULL, server->GetMV(inMailHdr.mvIndex));
				break;
				case CREATE_LOCK: 
					msgUtil->SendMessage(response, PktTo, THREAD_TO, SERVER_TO, NULL, NULL, NULL, NULL, server->GetNewLockIndex());
				break;
				case CREATE_CV: 
					msgUtil->SendMessage(response, PktTo, THREAD_TO, SERVER_TO, NULL, NULL, NULL, NULL, server->GetNewCVIndex());
				break;
			}
			PktTo = server->GetNextPktDest();
		}
	}
	interrupt->Halt();
}

void KillServer(){
	printf("Kill server\n");
	char *data = "Kill Serv";
	msgUtil->SendMessage(data, SERVER_ID, SERVER_TO, NULL, NULL, NULL, NULL, NULL, NULL);
	interrupt->Halt();
}

//server machine ID is the same for all
void
MailTest(int threadType)
{
	//MessageUtil msgutil = new MessageUtil();
	switch (threadType) {
		default:
			printf("Unknown type - shutting down.\n");
		case KILL_SERVER: //0-thread to kill server
			KillServer();
		break;
		case SERVER://1-server
			ServerManager();
		break;
		case SIMPLETHREAD: //2-simple user prog
			printf("Simple thread created\n");
			SimpleThread();
		break;
		case USERPROG1_THREAD1: //3-acquire,wait,acquire,release user prog
			printf("User prog 1 thread 1 created\n");
			Userprog1_Thread1();
		break;
		case USERPROG1_THREAD2: //4-acquire,broadcast,release user prog
			printf("User prog 1 thread 2 created\n");
			Userprog1_Thread2();
		break;
		case USERPROG1_THREAD3: //5-acquire,signal,release user prog
			printf("User prog 1 thread 3 created\n");
			Userprog1_Thread3();
		break;
		case USERPROG1_THREAD4: //5-acquire,signal,update and retrieve MVs,release user prog w/ new lock
			printf("User prog 1 thread 4 created\n");
			Userprog1_Thread4();
		break;
		case USERPROG1_THREAD5: //6-acquire,broadcast,release user prog w/ same lock as thread 4
			printf("User prog 1 thread 4 created\n");
			Userprog1_Thread5();
		break;
	}
    // Then we're done!
    interrupt->Halt();
}
